// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/wm/core/transient_window_manager.h"

#include <algorithm>
#include <functional>

#include "base/auto_reset.h"
#include "base/stl_util.h"
#include "ui/aura/window.h"
#include "ui/aura/window_property.h"
#include "ui/aura/window_tracker.h"
#include "ui/wm/core/transient_window_observer.h"
#include "ui/wm/core/transient_window_stacking_client.h"
#include "ui/wm/core/window_util.h"

using aura::Window;

DECLARE_WINDOW_PROPERTY_TYPE(wm::TransientWindowManager*);

namespace wm {
namespace {

    DEFINE_OWNED_WINDOW_PROPERTY_KEY(TransientWindowManager, kPropertyKey, NULL);

} // namespace

TransientWindowManager::~TransientWindowManager()
{
}

// static
TransientWindowManager* TransientWindowManager::Get(Window* window)
{
    TransientWindowManager* manager = window->GetProperty(kPropertyKey);
    if (!manager) {
        manager = new TransientWindowManager(window);
        window->SetProperty(kPropertyKey, manager);
    }
    return manager;
}

// static
const TransientWindowManager* TransientWindowManager::Get(
    const Window* window)
{
    return window->GetProperty(kPropertyKey);
}

void TransientWindowManager::AddObserver(TransientWindowObserver* observer)
{
    observers_.AddObserver(observer);
}

void TransientWindowManager::RemoveObserver(TransientWindowObserver* observer)
{
    observers_.RemoveObserver(observer);
}

void TransientWindowManager::AddTransientChild(Window* child)
{
    // TransientWindowStackingClient does the stacking of transient windows. If it
    // isn't installed stacking is going to be wrong.
    DCHECK(TransientWindowStackingClient::instance_);

    TransientWindowManager* child_manager = Get(child);
    if (child_manager->transient_parent_)
        Get(child_manager->transient_parent_)->RemoveTransientChild(child);
    DCHECK(std::find(transient_children_.begin(), transient_children_.end(),
               child)
        == transient_children_.end());
    transient_children_.push_back(child);
    child_manager->transient_parent_ = window_;

    // Restack |child| properly above its transient parent, if they share the same
    // parent.
    if (child->parent() == window_->parent())
        RestackTransientDescendants();

    FOR_EACH_OBSERVER(TransientWindowObserver, observers_,
        OnTransientChildAdded(window_, child));
}

void TransientWindowManager::RemoveTransientChild(Window* child)
{
    Windows::iterator i = std::find(transient_children_.begin(), transient_children_.end(), child);
    DCHECK(i != transient_children_.end());
    transient_children_.erase(i);
    TransientWindowManager* child_manager = Get(child);
    DCHECK_EQ(window_, child_manager->transient_parent_);
    child_manager->transient_parent_ = NULL;

    // If |child| and its former transient parent share the same parent, |child|
    // should be restacked properly so it is not among transient children of its
    // former parent, anymore.
    if (window_->parent() == child->parent())
        RestackTransientDescendants();

    FOR_EACH_OBSERVER(TransientWindowObserver, observers_,
        OnTransientChildRemoved(window_, child));
}

bool TransientWindowManager::IsStackingTransient(
    const aura::Window* target) const
{
    return stacking_target_ == target;
}

TransientWindowManager::TransientWindowManager(Window* window)
    : window_(window)
    , transient_parent_(NULL)
    , stacking_target_(NULL)
    , parent_controls_visibility_(false)
    , show_on_parent_visible_(false)
    , ignore_visibility_changed_event_(false)
{
    window_->AddObserver(this);
}

void TransientWindowManager::RestackTransientDescendants()
{
    Window* parent = window_->parent();
    if (!parent)
        return;

    // Stack any transient children that share the same parent to be in front of
    // |window_|. The existing stacking order is preserved by iterating backwards
    // and always stacking on top.
    Window::Windows children(parent->children());
    for (Window::Windows::reverse_iterator it = children.rbegin();
         it != children.rend(); ++it) {
        if ((*it) != window_ && HasTransientAncestor(*it, window_)) {
            TransientWindowManager* descendant_manager = Get(*it);
            base::AutoReset<Window*> resetter(
                &descendant_manager->stacking_target_,
                window_);
            parent->StackChildAbove((*it), window_);
        }
    }
}

void TransientWindowManager::OnWindowParentChanged(aura::Window* window,
    aura::Window* parent)
{
    DCHECK_EQ(window_, window);
    // Stack |window| properly if it is transient child of a sibling.
    Window* transient_parent = wm::GetTransientParent(window);
    if (transient_parent && transient_parent->parent() == parent) {
        TransientWindowManager* transient_parent_manager = Get(transient_parent);
        transient_parent_manager->RestackTransientDescendants();
    }
}

void TransientWindowManager::UpdateTransientChildVisibility(
    bool parent_visible)
{
    base::AutoReset<bool> reset(&ignore_visibility_changed_event_, true);
    if (!parent_visible) {
        show_on_parent_visible_ = window_->TargetVisibility();
        window_->Hide();
    } else {
        if (show_on_parent_visible_ && parent_controls_visibility_)
            window_->Show();
        show_on_parent_visible_ = false;
    }
}

void TransientWindowManager::OnWindowVisibilityChanged(Window* window,
    bool visible)
{
    if (window_ != window)
        return;

    // If the window has transient children, updates the transient children's
    // visiblity as well.
    // WindowTracker is used because child window
    // could be deleted inside UpdateTransientChildVisibility call.
    aura::WindowTracker tracker(transient_children_);
    while (tracker.has_windows())
        Get(tracker.Pop())->UpdateTransientChildVisibility(visible);

    // Remember the show request in |show_on_parent_visible_| and hide it again
    // if the following conditions are met
    // - |parent_controls_visibility| is set to true.
    // - the window is hidden while the transient parent is not visible.
    // - Show/Hide was NOT called from TransientWindowManager.
    if (ignore_visibility_changed_event_ || !transient_parent_ || !parent_controls_visibility_) {
        return;
    }

    if (!transient_parent_->TargetVisibility() && visible) {
        base::AutoReset<bool> reset(&ignore_visibility_changed_event_, true);
        show_on_parent_visible_ = true;
        window_->Hide();
    } else if (!visible) {
        DCHECK(!show_on_parent_visible_);
    }
}

void TransientWindowManager::OnWindowStackingChanged(Window* window)
{
    DCHECK_EQ(window_, window);
    // Do nothing if we initiated the stacking change.
    const TransientWindowManager* transient_manager = Get(static_cast<const Window*>(window));
    if (transient_manager && transient_manager->stacking_target_) {
        Windows::const_iterator window_i = std::find(
            window->parent()->children().begin(),
            window->parent()->children().end(),
            window);
        DCHECK(window_i != window->parent()->children().end());
        if (window_i != window->parent()->children().begin() && (*(window_i - 1) == transient_manager->stacking_target_))
            return;
    }

    RestackTransientDescendants();
}

void TransientWindowManager::OnWindowDestroying(Window* window)
{
    // Removes ourselves from our transient parent (if it hasn't been done by the
    // RootWindow).
    if (transient_parent_) {
        TransientWindowManager::Get(transient_parent_)->RemoveTransientChild(window_);
    }

    // Destroy transient children, only after we've removed ourselves from our
    // parent, as destroying an active transient child may otherwise attempt to
    // refocus us.
    Windows transient_children(transient_children_);
    STLDeleteElements(&transient_children);
    DCHECK(transient_children_.empty());
}

} // namespace wm
